#define __STDC_LIMIT_MACROS

#include <cassert>
#include <cerrno>
#include <climits>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <memory>
#include <stdexcept>
#include <unordered_map>

#include <fuse.h>
#include <syslog.h>

#include "dataNode.h"
#include "extraField.h"
#include "util.h"

DataNode::DataNode(uint64_t id, mode_t mode, uid_t uid, gid_t gid, dev_t dev)
{
    _id = id;
    _open_count = 0;
    _size = 0;
    _mode = mode;
    _uid = uid;
    _gid = gid;
    _device = dev;
}

std::shared_ptr<DataNode> DataNode::createNew(mode_t mode, uid_t uid, gid_t gid, dev_t dev)
{
    std::shared_ptr<DataNode> n(new DataNode(FAKE_ID, mode, uid, gid, dev));

    n->_state = NodeState::NEW;
    n->_buffer.reset(new BigBuffer());

    n->_has_btime = true;
    n->_metadataChanged = true;
    n->_mtime = n->_atime = n->_ctime = n->_btime = currentTime();

    return n;
}

std::shared_ptr<DataNode> DataNode::createExisting(uint64_t id, struct stat fileStat)
{
    std::shared_ptr<DataNode> n(new DataNode(id, fileStat.st_mode, fuse_get_context()->uid, fuse_get_context()->gid, 0));

    n->_state = NodeState::CLOSED;
    n->_metadataChanged = false;

    n->_mtime.tv_sec = n->_atime.tv_sec = n->_ctime.tv_sec = n->_btime.tv_sec = fileStat.st_mtime;
    n->_mtime.tv_nsec = n->_atime.tv_nsec = n->_ctime.tv_nsec = n->_btime.tv_nsec = 0;
    n->_has_btime = false;
    n->_size = static_cast<uint64_t>(fileStat.st_size);
    return n;
}

std::shared_ptr<DataNode> DataNode::createTmpDir(mode_t mode, uid_t uid, gid_t gid, dev_t dev)
{
    std::shared_ptr<DataNode> n(new DataNode(FAKE_ID, mode, uid, gid, dev));

    n->_state = NodeState::NEW;
    n->_buffer.reset(new BigBuffer());

    n->_has_btime = true;
    n->_metadataChanged = false;
    n->_mtime = n->_atime = n->_ctime = n->_btime = currentTime();

    return n;
}

uint64_t DataNode::size() const
{
    if (_state == NodeState::NEW || _state == NodeState::OPENED || _state == NodeState::CHANGED ||
        _state == NodeState::VIRTUAL_SYMLINK)
    {
        return _buffer->len;
    }
    else
    {
        return _size;
    }
}

void DataNode::chmod(mode_t mode)
{
    _mode = (_mode & S_IFMT) | mode;
    _ctime = currentTime();
    _metadataChanged = true;
}

void DataNode::setUid(uid_t uid)
{
    _uid = uid;
    _metadataChanged = true;
}

void DataNode::setGid(gid_t gid)
{
    _gid = gid;
    _metadataChanged = true;
}

void DataNode::setTimes(const timespec &atime, const timespec &mtime)
{
    struct timespec now;
    if (atime.tv_nsec == UTIME_NOW || mtime.tv_nsec == UTIME_NOW)
        now = currentTime();
    if (atime.tv_nsec == UTIME_NOW)
        _atime = now;
    else if (atime.tv_nsec != UTIME_OMIT)
        _atime = atime;
    if (mtime.tv_nsec == UTIME_NOW)
        _mtime = now;
    else if (mtime.tv_nsec != UTIME_OMIT)
        _mtime = mtime;
    _metadataChanged = true;
}

void DataNode::setCTime(const timespec &ctime)
{
    _ctime = ctime;
    _metadataChanged = true;
}

void DataNode::touchCTime()
{
    _ctime = currentTime();
}

int DataNode::open()
{
    if (_state == NodeState::NEW || _state == NodeState::VIRTUAL_SYMLINK)
    {
        return 0;
    }
    if (_state == NodeState::OPENED)
    {
        if (_open_count == INT_MAX)
        {
            return -EMFILE;
        }
        else
        {
            ++_open_count;
        }
    }
    if (_state == NodeState::CLOSED)
    {
        _open_count = 1;
        try
        {
            if (_size > std::numeric_limits<size_t>::max())
            {
                return -ENOMEM;
            }
            assert(_id != FAKE_ID);
            _buffer.reset(new BigBuffer(static_cast<size_t>(_size)));
            _state = NodeState::OPENED;
        }
        catch (std::bad_alloc &)
        {
            return -ENOMEM;
        }
        catch (std::exception &)
        {
            return -EIO;
        }
    }
    return 0;
}

int DataNode::read(char *buf, size_t sz, size_t offset) {
    _atime = currentTime();
    return _buffer->read(buf, sz, offset);
}

int DataNode::file_read(char *buf, size_t sz, size_t offset, const char *real_name, uint64_t real_file_size)
{
    _atime = currentTime();
    return _buffer->file_read(buf, sz, offset, real_name, real_file_size);
}

int DataNode::vsphere_read(char *buf, size_t sz, size_t offset, uint64_t real_file_size, std::vector<int> &fd_vector, const int store_file, const std::unordered_map<int, std::vector<DataSegment>>& cache)
{
    _atime = currentTime();
    return _buffer->vsphere_read(buf, sz, offset, real_file_size, fd_vector, store_file, cache);
}

int DataNode::fsphere_read(char *buf, size_t sz, size_t offset, const char *real_name, uint64_t real_file_size, const char *store_name)
{
    _atime = currentTime();
    return _buffer->fsphere_read(buf, sz, offset, real_name, real_file_size, store_name);
}

int DataNode::write(const char *buf, size_t sz, size_t offset) {
    assert(_state != NodeState::VIRTUAL_SYMLINK);
    if (_state == NodeState::OPENED) {
        _state = NodeState::CHANGED;
    }
    _mtime = currentTime();
    _metadataChanged = true;
    return _buffer->write(buf, sz, offset);
}

int DataNode::file_write(const char *buf, size_t sz, size_t offset, const char *real_name, uint64_t real_file_size) {
    assert(_state != NodeState::VIRTUAL_SYMLINK);
    if (_state == NodeState::OPENED) {
        _state = NodeState::CHANGED;
    }
    _mtime = currentTime();
    _metadataChanged = true;
    return _buffer->file_write(buf, sz, offset, real_name, real_file_size);
}

int DataNode::vsphere_write(const char *buf, size_t sz, size_t offset, uint64_t real_file_size, std::vector<int> &fd_vector, const int store_file, const std::unordered_map<int, std::vector<DataSegment>>& cache) {
    assert(_state != NodeState::VIRTUAL_SYMLINK);
    if (_state == NodeState::OPENED) {
        _state = NodeState::CHANGED;
    }
    _mtime = currentTime();
    _metadataChanged = true;
    return _buffer->vsphere_write(buf, sz, offset, real_file_size, fd_vector, store_file, cache);
}

int DataNode::fsphere_write(const char *buf, size_t sz, size_t offset, const char *real_name, uint64_t real_file_size, const char *store_name) {
    assert(_state != NodeState::VIRTUAL_SYMLINK);
    if (_state == NodeState::OPENED) {
        _state = NodeState::CHANGED;
    }
    _mtime = currentTime();
    _metadataChanged = true;
    return _buffer->fsphere_write(buf, sz, offset, real_name, real_file_size, store_name);
}

int DataNode::close()
{
    _size = _buffer->len;
    if (_state == NodeState::OPENED && --_open_count == 0)
    {
        _buffer.reset();
        _state = NodeState::CLOSED;
    }
    return 0;
}

int DataNode::truncate(size_t offset) {
    assert(_state != NodeState::VIRTUAL_SYMLINK);
    if (_state != NodeState::CLOSED) {
        if (_state != NodeState::NEW) {
            _state = NodeState::CHANGED;
        }
        try {
            _buffer->truncate(offset);
            return 0;
        }
        catch (const std::bad_alloc &) {
            return EIO;
        }
        _mtime = currentTime();
        _metadataChanged = true;
    } else {
        return EBADF;
    }
}